package coreModel;

import hitExceptions.InvalidOperationException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import common.Result;

import dataPersistence.dao.IProductDAO;
import dataPersistence.dto.ProductDTO;


@SuppressWarnings("serial")
public class ProductManager extends SerializableImplementer{

	//Maps from Product to ProductContainers
	private Map<Product, Collection<ProductContainer>> productContainerMap;
	//Maps from storageUnit to Products
	private Map<ProductContainer, Collection<Product>> containerProductMap;
	//All products in the program
	private Collection<Product> allProducts;

	private boolean duplicateFound;

	/**
	 * Constructor inits all indexes
	 */
	public ProductManager()
	{
		productContainerMap= new HashMap<Product, Collection<ProductContainer>>();
		containerProductMap = new HashMap<ProductContainer,Collection<Product>>();
		allProducts= new ArrayList<Product>();
	}

	/**
	 * Adds a product to a container
	 * @param product the product obj to be added
	 * @param container the container which holds the product
	 * @throws InvalidOperationException when a Product already exists.
	 */
	public Result addProductToContainer(Product product, 
			ProductContainer container) throws InvalidOperationException 
			{
		Result result = new Result();

		duplicateFound = false;

		if(container instanceof ProductGroup) 
			containsProductInContainerRecurse(product, ((ProductGroup)container).getParent());
		else
			containsProductInContainerRecurse(product, container);


		if(duplicateFound == true)
			throw new InvalidOperationException("The product already exists in the container");


		/*

		if(containsProductInContainer(product, container))
			throw new InvalidOperationException("The product already exists in the container");

		 */

		//getDAO
		IProductDAO dao = HIT.getInstance().getFactory().getProductDAO();
		ProductDTO dto = new ProductDTO(product);

		//not found in Map or map is empty
		if(productContainerMap.isEmpty() || !productContainerMap.containsKey(product))
		{
			//added container to db online if not loading from db
			if(!HIT.getInstance().isLoadingFromDataBase())
			{
				result=dao.addProductToContainer(dto, container.getID());
				if(!result.getStatus())
				{
					System.err.println(result.getMessage());
				}
			}

			//create entry in Map
			productContainerMap.put(product,new ArrayList<ProductContainer>());
			//add to product container list
			productContainerMap.get(product).add(container);
		}
		//found in map
		else
		{
			//added container to db
			if(!HIT.getInstance().isLoadingFromDataBase())
			{
				result=dao.addProductToContainer(dto, container.getID());
				if(!result.getStatus())
				{
					System.err.println(result.getMessage());
				}
			}

			//add to product container list
			productContainerMap.get(product).add(container);
		}


		// not found in map or map is empty
		if(containerProductMap.isEmpty() ||  !containerProductMap.containsKey(container))
		{

			//create entry
			containerProductMap.put(container,new ArrayList<Product>());
			//adds to productContainer list
			containerProductMap.get(container).add(product);

		}
		//found in map
		else
		{
			containerProductMap.get(container).add(product);
		}

		//check that product is in allProd list if not in list add it
		addProductToAllProducts(product);

		//return result object
		return result;
			}

	/**
	 * Adds a product to the list of all products in the System. This is useful
	 * when added used products to the master list.
	 * @param product
	 */
	public void addProductToAllProducts(Product product)
	{
		if(!allProducts.contains(product))
		{
			IProductDAO dao = HIT.getInstance().getFactory().getProductDAO();

			ProductDTO dto = new ProductDTO(product);

			Result result = new Result();
			//only add if not loading from db
			if(!HIT.getInstance().isLoadingFromDataBase())
			{
				result=dao.add(dto);
				if(!result.getStatus()) System.err.println(result.getMessage());	
			}

			//add to allProd list
			allProducts.add(product);
			//create entry in Map 
			// I am not sure if this is needed emailing Tyler because he created it
			if(!productContainerMap.containsKey(product))
				productContainerMap.put(product,new ArrayList<ProductContainer>());
			//added the if statement because the new arrayList was over writing the content
			//of the map.
		}
	}

	/**
	 * returns a unmodifiable collection of container that have inside of them this product
	 * @param product the product object we want to know where it is
	 * @return an unmodifiableCollection if product container or null if product is not found
	 */
	public Collection<ProductContainer> getContainersForProduct(Product product)
	{

		//Product is not found in the list
		if(productContainerMap.isEmpty() || !productContainerMap.containsKey(product))
		{
			return new ArrayList<ProductContainer>();
		}
		return Collections.unmodifiableCollection(productContainerMap.get(product));
	}

	/**
	 * returns an unmodifiable collection of products that are in the container obj that was 
	 * passed in
	 * @param container 
	 * @return an unmodifiable collection of products or null if it is empty
	 */
	public Collection<Product> getProductsOfContainer(ProductContainer container)
	{
		if(containerProductMap.get(container)==null)
		{
			return new ArrayList<Product>();
		}
		else
			return Collections.unmodifiableCollection(containerProductMap.get(container));
	}

	/**
	 * moves all items of type product in origin container to destination container
	 * also moves product instances to destination container. calls item manager
	 * NOTE THIS FUNCTION ONLY MOVES INSIDE A CONTAINER
	 * @param product the product to move
	 * @param origin the origin productContainer
	 * @param destination the destinationContainer
	 */
	public void moveProduct(Product product, ProductContainer origin,
			ProductContainer destination) throws InvalidOperationException
			{

		//remove prod from origin
		containerProductMap.get(origin).remove(product);
		productContainerMap.get(product).remove(origin);

		//if it is the first product in this container
		if(!containerProductMap.containsKey(destination))
			containerProductMap.put(destination, new ArrayList<Product>());

		//add prod to destination
		containerProductMap.get(destination).add(product);
		productContainerMap.get(product).add(destination);


		//	HIT.getInstance().getItemManager().addContainer(destination);
		//change all items //destination is always what it is
		HIT.getInstance().getItemManager().moveItemsByProduct(product,origin,destination);

			}

	/**
	 * Removes product obj from the specified container	
	 * @param product the product to remove
	 * @param container the container to remove if from
	 * @throws InvalidOperationException if product is not found
	 */
	public void removeProductFromContainer(Product product,
			ProductContainer container) throws InvalidOperationException
			{

		if(!canRemoveProductFromContainer(product, container))
			throw new InvalidOperationException("Product can't be removed until it is empty");
		//removes from product container list
		if(productContainerMap.isEmpty() ||  !productContainerMap.containsKey(product))
			throw new InvalidOperationException("product is not found in the product manager");

		productContainerMap.get(product).remove(container);


		//remove from productContainer list
		if(!containerProductMap.containsKey(container))
			throw new InvalidOperationException("Container is not found in the product manager");
		containerProductMap.get(container).remove(product);

		//this is not correct

		//		// it no instances of that product exist remove it from the list
		//		if(productContainerMap.get(product).isEmpty())
		//		{
		//			allProducts.remove(product);
		//			productContainerMap.remove(product);
		//		}

			}

	public boolean canRemoveProductFromContainer(Product prod,ProductContainer container)
	{
		return HIT.getInstance().getItemManager().canRemove(prod,container);
	}

	/**
	 * removes a container from the product list from HIT
	 * @throws InvalidOperationException When a container is non empty and 
	 * it is trying to be removed.
	 */
	public void removeContainer(ProductContainer container) throws InvalidOperationException
	{
		if(!container.canRemoveContainer())
		{
			throw new InvalidOperationException("A container must be empty to remove it");
		}

		//remove from products from container
		containerProductMap.remove(container);

		for(Product product: productContainerMap.keySet()) 
			// for each entry in container to product map
		{
			if(productContainerMap.get(product).contains(container))
				//check to see if the container is in the list of prods
			{
				productContainerMap.get(product).remove(container); // remove
			}
		}

	}

	/**
	 * Returns all products that are in the system
	 * @return unmodifiable collection of products
	 */
	public Collection<Product> getAllProducts()
	{
		return Collections.unmodifiableCollection(allProducts);
	}


	/**
	 * (ADDED BY TYLER FOR ADDITEMBATCHCONTROLLER)
	 * Returns the product in the system defined by the given barcode.
	 * If the barcode does not define a product, returns null.
	 * @param barcode The barcode for which to find a product
	 * @return The Product defined by barcode, or null if there isn't one.
	 */
	public Product getProductByBarcode(Barcode barcode)
	{
		for (Product p : getAllProducts())
		{
			if (p != null && p.getBarcode().equals(barcode))
				return p;
		}
		return null;
	}


	/**
	 * Return true if a product is in the given container
	 * @param product 
	 * @param container
	 * @return true iff prod is in container
	 */
	public boolean containsProductInContainer(Product product, ProductContainer container)
	{

		if(containerProductMap.size()==0) return false;

		if(containerProductMap.get(container)==null) return false;

		return containerProductMap.get(container).contains(product);


		/*
		if(containerProductMap.size()==0) return false;
		if(!containerProductMap.containsKey(container))
			return false;
		return containerProductMap.get(container).contains(product);
		 */

		/*
		// if the list is empty or it doesn't contain the container return false
		if(containerProductMap.isEmpty() || !containerProductMap.containsKey(container))
			return false;
		else
			return containerProductMap.get(container).contains(product);
		 */

	}

	/**
	 * Returns null if the product is not found in the storageUnit else returns the
	 *  productContainer that it is found in
	 * @param product
	 * @param productContainer
	 * @return
	 */
	public ProductContainer locationOfProductInContainer(Product product,
			ProductContainer productContainer)
	{
		//init of stack
		Stack<ProductContainer> containerStack= new Stack<ProductContainer>();
		containerStack.push(productContainer);

		while(!containerStack.isEmpty())
		{
			ProductContainer tmpCont= containerStack.pop();
			if(containsProductInContainer(product,tmpCont))
			{
				return tmpCont;
			}
			else
			{
				for(ProductContainer child: tmpCont.getChildren())
				{
					containerStack.push(child);
				}
			}

		}
		return null;
	}


	public void containsProductInContainerRecurse(Product product, ProductContainer container){


		if(containsProductInContainer(product, container)) duplicateFound = true;

		ArrayList<ProductGroup> groups = (ArrayList<ProductGroup>)container.getChildren();

		if(groups.size()==0) return;


		for(int i=0;i<groups.size();i++){

			containsProductInContainerRecurse(product, groups.get(i));

		}

	}






	public boolean productExists(Product p)
	{
		if(allProducts.contains(p))
			return true;
		return false;
	}

	public void removeProductFromAllList(Product p)
	{
		allProducts.remove(p);
	}

	public void addConatiner(ProductContainer su)
	{
		if(!containerProductMap.containsKey(su))
		{
			containerProductMap.put(su, new ArrayList<Product>());
		}
	}

	public boolean hasContainer(ProductContainer pc)
	{
		return containerProductMap.containsKey(pc);
	}




}
